﻿using EnvDTE;
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Process = System.Diagnostics.Process;

namespace Red.Controls.Utils
{
    /// <summary>
    /// Helper class for communication with Visual Studio 2012
    /// </summary>
    public class VisualStudioHelper
    {
        #region Public Methods

        /// <summary>
        /// Opens file in Visual Studio 2012 and selects proper line
        /// </summary>
        /// <param name="file">File path to open</param>
        /// <param name="line">Line number to select</param>
        public static void OpenLocalization( string file, int line )
        {
            try
            {
                var currentProcess = Process.GetCurrentProcess();
                var parentProcess = GetParentProcess( currentProcess );
                var dte = GetDteObjectFromProcessId( parentProcess.Id );

                EnvDTE80.DTE2 dte2 = (EnvDTE80.DTE2)dte;
                dte2.MainWindow.Activate();

                Window w = dte2.ItemOperations.OpenFile( file );
                w.Activate();

                TextSelection sel = dte2.ActiveDocument.Selection;
                sel.GotoLine( line, true );
            }
            catch ( Exception e )
            {
                Console.Write( e.Message );
            }
        }

        #endregion

        #region Private Methods

        [StructLayout( LayoutKind.Sequential )]
        private struct PROCESSENTRY32
        {
            public uint dwSize;
            public uint cntUsage;
            public uint th32ProcessID;
            public IntPtr th32DefaultHeapID;
            public uint th32ModuleID;
            public uint cntThreads;
            public uint th32ParentProcessID;
            public int pcPriClassBase;
            public uint dwFlags;
            [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 260 )]
            public string szExeFile;
        };

        [DllImport( "ole32.dll" )]
        private static extern void CreateBindCtx( int reserved, out IBindCtx ppbc );
        [DllImport( "ole32.dll" )]
        private static extern void GetRunningObjectTable( int reserved, out IRunningObjectTable prot );
        [DllImport( "kernel32.dll", SetLastError = true )]
        private static extern IntPtr CreateToolhelp32Snapshot( uint dwFlags, uint th32ProcessID );
        [DllImport( "kernel32.dll" )]
        private static extern bool Process32First( IntPtr hSnapshot, ref PROCESSENTRY32 lppe );
        [DllImport( "kernel32.dll" )]
        private static extern bool Process32Next( IntPtr hSnapshot, ref PROCESSENTRY32 lppe );

        private static Process GetParentProcess( Process process )
        {
            int iParentPid = 0;
            int iCurrentPid = process.Id;
            uint TH32CS_SNAPPROCESS = 2;

            IntPtr oHnd = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );

            if ( oHnd == IntPtr.Zero )
                return null;

            PROCESSENTRY32 oProcInfo = new PROCESSENTRY32();

            oProcInfo.dwSize = (uint)Marshal.SizeOf( typeof( PROCESSENTRY32 ) );

            if ( Process32First( oHnd, ref oProcInfo ) == false )
                return null;

            do
            {
                if ( iCurrentPid == oProcInfo.th32ProcessID )
                    iParentPid = (int)oProcInfo.th32ParentProcessID;
            }
            while ( iParentPid == 0 && Process32Next( oHnd, ref oProcInfo ) );

            if ( iParentPid > 0 )
                return Process.GetProcessById( iParentPid );

            return null;
        }

        private static DTE GetDteObjectFromProcessId( int processId )
        {
            string rotEntry = String.Format( "!VisualStudio.DTE.14.0:{0}", processId );
            IRunningObjectTable rot;
            GetRunningObjectTable( 0, out rot );
            IEnumMoniker enumMoniker;
            rot.EnumRunning( out enumMoniker );
            enumMoniker.Reset();
            IntPtr fetched = IntPtr.Zero;
            IMoniker[] moniker = new IMoniker[1];
            while ( enumMoniker.Next( 1, moniker, fetched ) == 0 )
            {
                IBindCtx bindCtx;
                CreateBindCtx( 0, out bindCtx );
                string displayName;
                moniker[0].GetDisplayName( bindCtx, null, out displayName );
                if ( displayName == rotEntry )
                {
                    object comObject;
                    rot.GetObject( moniker[0], out comObject );
                    return (DTE)comObject;
                }
            }
            return null;
        }

        #endregion
    }
}
